// This file has been prepared for Doxygen automatic documentation generation.
/*! \file ********************************************************************
 *
 * Atmel Corporation
 *
 * \li File:               eeprom.c
 * \li Compiler:           IAR EWAAVR 3.10c
 * \li Support mail:       avr@atmel.com
 *
 * \li Supported devices:  All devices with split EEPROM erase/write
 *                         capabilities can be used.
 *                         The example is written for ATmega48.
 *
 * \li AppNote:            AVR103 - Using the EEPROM Programming Modes.
 *
 * \li Description:        Example on how to use the split EEPROM erase/write
 *                         capabilities in e.g. ATmega48. All EEPROM
 *                         programming modes are tested, i.e. Erase+Write,
 *                         Erase-only and Write-only.
 *
 *                         $Revision: 1.6 $
 *                         $Date: Friday, February 11, 2005 07:16:44 UTC $
 ****************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>

/* These EEPROM bits have different names on different devices. */
#ifndef EEPE
#define EEPE  EEWE  //!< EEPROM program/write enable.
#define EEMPE EEMWE //!< EEPROM master program/write enable.
#endif

/* These two are unfortunately not defined in the device include files. */
#define EEPM1 5 //!< EEPROM Programming Mode Bit 1.
#define EEPM0 4 //!< EEPROM Programming Mode Bit 0.

/* Define to reduce code size. */
#define EEPROM_IGNORE_SELFPROG //!< Remove SPM flag polling.

/*! \brief  Read byte from EEPROM.
 *
 *  This function reads one byte from a given EEPROM address.
 *
 *  \note  The CPU is halted for 4 clock cycles during EEPROM read.
 *
 *  \param  addr  EEPROM address to read from.
 *  \return  The byte read from the EEPROM address.
 */
unsigned char eeprom_get_char(unsigned int addr) {
    do {
    } while (EECR & (1 << EEPE)); // Wait for completion of previous write.
    EEAR = addr; // Set EEPROM address register.
    EECR = (1 << EERE); // Start EEPROM read operation.
    return EEDR; // Return the byte read from EEPROM.
}

/*! \brief  Write byte to EEPROM.
 *
 *  This function writes one byte to a given EEPROM address.
 *  The differences between the existing byte and the new value is used
 *  to select the most efficient EEPROM programming mode.
 *
 *  \note  The CPU is halted for 2 clock cycles during EEPROM programming.
 *
 *  \note  When this function returns, the new EEPROM value is not available
 *         until the EEPROM programming time has passed. The EEPE bit in EECR
 *         should be polled to check whether the programming is finished.
 *
 *  \note  The EEPROM_GetChar() function checks the EEPE bit automatically.
 *
 *  \param  addr  EEPROM address to write to.
 *  \param  new_value  New EEPROM value.
 */
void eeprom_put_char(unsigned int addr, unsigned char new_value) {
    char old_value; // Old EEPROM value.
    char diff_mask; // Difference mask, i.e. old value XOR new value.

    cli(); // Ensure atomic operation for the write operation.

    do {
    } while (EECR & (1 << EEPE)); // Wait for completion of previous write.
#ifndef EEPROM_IGNORE_SELFPROG
    do {
    } while (SPMCSR & (1 << SELFPRGEN)); // Wait for completion of SPM.
#endif

    EEAR = addr; // Set EEPROM address register.
    EECR = (1 << EERE); // Start EEPROM read operation.
    old_value = EEDR; // Get old EEPROM value.
    diff_mask = old_value ^ new_value; // Get bit differences.

    // Check if any bits are changed to '1' in the new value.
    if (diff_mask & new_value) {
        // Now we know that _some_ bits need to be erased to '1'.

        // Check if any bits in the new value are '0'.
        if (new_value != 0xff) {
            // Now we know that some bits need to be programmed to '0' also.

            EEDR = new_value; // Set EEPROM data register.
            EECR = (1 << EEMPE) | // Set Master Write Enable bit...
                    (0 << EEPM1) | (0 << EEPM0); // ...and Erase+Write mode.
            EECR |= (1 << EEPE); // Start Erase+Write operation.
        } else {
            // Now we know that all bits should be erased.

            EECR = (1 << EEMPE) | // Set Master Write Enable bit...
                    (1 << EEPM0); // ...and Erase-only mode.
            EECR |= (1 << EEPE); // Start Erase-only operation.
        }
    } else {
        // Now we know that _no_ bits need to be erased to '1'.

        // Check if any bits are changed from '1' in the old value.
        if (diff_mask) {
            // Now we know that _some_ bits need to the programmed to '0'.

            EEDR = new_value; // Set EEPROM data register.
            EECR = (1 << EEMPE) | // Set Master Write Enable bit...
                    (1 << EEPM1); // ...and Write-only mode.
            EECR |= (1 << EEPE); // Start Write-only operation.
        }
    }

    sei(); // Restore interrupt flag state.
}

// Extensions added as part of Grbl 

void memcpy_to_eeprom_with_checksum(unsigned int destination, char *source, unsigned int size) {
    unsigned char checksum = 0;
    for (; size > 0; size--) {
        checksum = (checksum << 1) || (checksum >> 7);
        checksum += *source;
        eeprom_put_char(destination++, *(source++));
    }
    eeprom_put_char(destination, checksum);
}

int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size) {
    unsigned char data, checksum = 0;
    for (; size > 0; size--) {
        data = eeprom_get_char(source++);
        checksum = (checksum << 1) || (checksum >> 7);
        checksum += data;
        *(destination++) = data;
    }
    return (checksum == eeprom_get_char(source));
}

// end of file
